package mirrormonkey.rpc;

import static org.junit.Assert.assertEquals;
import junit.framework.Assert;
import mirrormonkey.rpc.proxy.EntityProxy;
import mirrormonkey.rpc.tools.applications.ClientTestApplication;
import mirrormonkey.rpc.tools.applications.ServerTestApplication;
import mirrormonkey.rpc.tools.listeners.MockRpcListener;
import mirrormonkey.rpc.tools.multicall.entities.IntegerRpc;
import mirrormonkey.rpc.tools.multicall.specs.IntegerRpcSpec;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.jme3.network.HostedConnection;
import com.jme3.system.JmeContext;

public class BroadcastInvocationHandlerTest {

	public static final int CLIENT_COUNT = 2;

	public static final int ENTITY_COUNT = 10;

	public class ServerApplicationHandle {

		ServerTestApplication application;

		IntegerRpc[] entities;

		public ServerApplicationHandle() throws Exception {
			application = new ServerTestApplication();
			application.start();

			entities = new IntegerRpc[ENTITY_COUNT];
			for (int i = 0; i < entities.length; i++) {
				entities[i] = new IntegerRpc();
			}
		}

	}

	public class ClientApplicationHandle {

		ClientTestApplication application;

		HostedConnection serverLink;

		IntegerRpc[] entities;

		public ClientApplicationHandle() throws Exception {
			application = new ClientTestApplication();
			application.start(JmeContext.Type.Headless);

			sleepNoInterrupt(100);

			serverLink = serverHandle.application.appState.server
					.getConnection(application.appState.client.getId());

			for (IntegerRpc h : serverHandle.entities) {
				serverHandle.application.coreModule.addMapping(serverLink, h,
						IntegerRpc.class);
			}

			sleepNoInterrupt(100);
			application.appState.eventManager.processEvents();

			entities = new IntegerRpc[ENTITY_COUNT];
			for (int i = 0; i < ENTITY_COUNT; i++) {
				entities[i] = (IntegerRpc) application.appState.entityProvider
						.getData(serverHandle.entities[i].getData().id).entity;
			}
		}
	}

	public ServerApplicationHandle serverHandle;

	public ClientApplicationHandle[] clientHandles;

	public void sleepNoInterrupt(long millis) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {
		}
	}

	@Before
	public void setUp() throws Exception {
		serverHandle = new ServerApplicationHandle();

		clientHandles = new ClientApplicationHandle[CLIENT_COUNT];

		for (int i = 0; i < CLIENT_COUNT; i++) {
			clientHandles[i] = new ClientApplicationHandle();
		}
		sleepNoInterrupt(1000);
	}

	@After
	public void tearDown() {
		sleepNoInterrupt(1000);
		serverHandle.application.stop();
		serverHandle = null;
		for (int i = 0; i < CLIENT_COUNT; i++) {
			clientHandles[i].application.stop();
		}
		clientHandles = null;
		sleepNoInterrupt(1000);
	}

	public void refresh() {
		sleepNoInterrupt(100);
		for (ClientApplicationHandle h : clientHandles) {
			h.application.appState.eventManager.processEvents();
		}
		sleepNoInterrupt(100);
		serverHandle.application.appState.eventManager.processEvents();
	}

	public void assertValueEquals(ClientApplicationHandle h, int lower,
			int upper, int value, boolean b) {
		for (int i = lower; i < upper; i++) {
			assertEquals(b, value == h.entities[i].value);
		}
	}

	@Test
	public void testEntityBroadcast() {
		ClientApplicationHandle client = clientHandles[0];
		EntityProxy<IntegerRpcSpec> proxy = serverHandle.application.rpcModule
				.createRpcProxy(IntegerRpcSpec.class);
		MockRpcListener results = new MockRpcListener();
		proxy.addRpcListener(results);
		proxy.addMessageConnection(client.serverLink);

		proxy.getCallTarget().setValue(0);
		refresh();

		Assert.assertTrue(results.recordedEvents.isEmpty());

		int callId = 0;
		for (int i = 0; i < ENTITY_COUNT; i++) {
			proxy.addTargetEntity(serverHandle.entities[i]);
			proxy.getCallTarget().setValue(++callId);

			refresh();
			assertValueEquals(client, 0, i + 1, callId, true);
			assertValueEquals(client, i + 1, CLIENT_COUNT, callId, false);
			assertValueEquals(clientHandles[1], 0, CLIENT_COUNT, callId, false);

			assertEquals(i + 1, results.recordedEvents.size());
			for (int j = 0; j <= i; j++) {
				results.checkResultArrived(client.serverLink, callId);
			}
		}

		for (int i = 0; i < ENTITY_COUNT; i++) {
			proxy.removeTargetEntity(serverHandle.entities[i]);
			proxy.getCallTarget().setValue(++callId);

			refresh();
			assertValueEquals(client, 0, i + 1, callId, false);
			assertValueEquals(client, i + 1, CLIENT_COUNT, callId, true);
			assertValueEquals(clientHandles[1], 0, CLIENT_COUNT, callId, false);

			assertEquals(ENTITY_COUNT - i - 1, results.recordedEvents.size());
			for (int j = i + 1; j < ENTITY_COUNT; j++) {
				results.checkResultArrived(client.serverLink, callId);
			}
		}

	}

	@Test
	public void testEmptyProxy() {
		IntegerRpc entity = serverHandle.entities[0];
		EntityProxy<IntegerRpcSpec> proxy = serverHandle.application.rpcModule
				.createRpcProxy(IntegerRpcSpec.class);
		MockRpcListener results = new MockRpcListener();
		proxy.addRpcListener(results);

		proxy.getCallTarget().setValue(0);
		refresh();
		Assert.assertTrue(results.recordedEvents.isEmpty());
	}

	@Test
	public void testClientBroadcast() {
		IntegerRpc entity = serverHandle.entities[0];
		EntityProxy<IntegerRpcSpec> proxy = serverHandle.application.rpcModule
				.createRpcProxy(IntegerRpcSpec.class);
		MockRpcListener results = new MockRpcListener();
		proxy.addRpcListener(results);
		proxy.addTargetEntity(entity);

		proxy.getCallTarget().setValue(0);
		refresh();

		Assert.assertTrue(results.recordedEvents.isEmpty());

		for (int i = 0; i < CLIENT_COUNT; i++) {
			proxy.addMessageConnection(clientHandles[i].serverLink);
		}

		proxy.getCallTarget().setValue(100);
		refresh();

		for (int i = 0; i < CLIENT_COUNT; i++) {
			assertValueEquals(clientHandles[i], 0, 1, 100, true);
			assertValueEquals(clientHandles[i], 1, ENTITY_COUNT, 100, false);
		}

		assertEquals(CLIENT_COUNT, results.recordedEvents.size());
	}

}
